package aceim.app.utils; /** * * Native LED controller is created by apangin ( http://apangin.habrahabr.ru/ ) */ import java.io.File; import java.io.FileWriter; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.Executors; import aceim.api.dataentity.Buddy; import aceim.api.dataentity.ConnectionState; import aceim.api.dataentity.FileInfo; import aceim.api.dataentity.FileMessage; import aceim.api.dataentity.FileProgress; import aceim.api.dataentity.Message; import aceim.api.dataentity.ServiceMessage; import aceim.api.dataentity.TextMessage; import aceim.api.service.ProtocolException; import aceim.api.utils.Logger; import aceim.api.utils.Logger.LoggerLevel; import aceim.app.AceImException; import aceim.app.Constants; import aceim.app.MainActivity; import aceim.app.R; import aceim.app.dataentity.Account; import aceim.app.dataentity.AccountService; import aceim.app.service.CoreService; import aceim.app.view.page.Page; import aceim.app.view.page.chat.Chat; import aceim.app.view.page.contactlist.ContactList; import android.annotation.SuppressLint; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.graphics.BitmapFactory; import android.media.AudioManager; import android.media.MediaPlayer; import android.net.Uri; import android.os.Build; import android.os.Vibrator; import android.support.v4.app.NotificationCompat; import android.support.v4.app.NotificationCompat.Builder; import android.text.TextUtils; public class Notificator { private static final String URI_PROTOCOL = "aceim://"; private float mVolumeLevel = 1f; private Context mContext; private final NotificationManager mNotificatorManager; private final Vibrator mVibrator; private final AudioManager mAudioManager; private static final int APP_ICON_ID = -100500; private LedBlinker mLedBlinker; private final Map<Long, NotificationCompat.Builder> mFileTransferViews = new HashMap<Long, NotificationCompat.Builder>(); private SoundNotificationMode mSoundMode = SoundNotificationMode.PROFILE_DEPENDENT; private StatusBarNotificationMode mStatusBarMode = StatusBarNotificationMode.APP_ICON; private LEDNotificationMode mLEDNotificationMode = LEDNotificationMode.DEFAULT_BLINK; private boolean messageSoundOnly = false; public Notificator(Context context) { this.mContext = context; this.mNotificatorManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); this.mVibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE); this.mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); } public void onMessage(Message message, Account account) { Logger.log("Notification for message" + message, LoggerLevel.VERBOSE); Buddy buddy = account.getBuddyByProtocolUid(message.getContactUid()); NotificationCompat.Builder builder = new NotificationCompat.Builder(mContext); builder.setAutoCancel(true); builder.setContentTitle(buddy.getSafeName()); builder.setSubText(account.getSafeName()); builder.setWhen(System.currentTimeMillis()); Intent notificationIntent = new Intent(mContext, MainActivity.class); fillMessageIntent(notificationIntent, account, buddy, message); String text; if (message instanceof TextMessage) { notificationIntent.putExtra(Constants.INTENT_EXTRA_CLASS_NAME, Chat.class.getName()); notificationIntent.setData(ViewUtils.stringAsIntentDataUri(MainActivity.class.getName())); text = message.getText(); if (buddy.getUnread() > 1) { builder.setNumber(buddy.getUnread()); } builder.setSmallIcon(R.drawable.ic_message); } else if (message instanceof FileMessage || ((message instanceof ServiceMessage) && ((ServiceMessage)message).isRequireAcceptDeclineAnswer())) { Intent acceptIntent = new Intent(mContext.getApplicationContext(), CoreService.class); fillMessageIntent(acceptIntent, account, buddy, message); acceptIntent.setData(ViewUtils.stringAsIntentDataUri(mContext.getString(android.R.string.ok))); Intent declineIntent = new Intent(mContext.getApplicationContext(), CoreService.class); fillMessageIntent(declineIntent, account, buddy, message); declineIntent.setData(ViewUtils.stringAsIntentDataUri(mContext.getString(android.R.string.cancel))); PendingIntent acceptPIntent = PendingIntent.getService(mContext, 0, acceptIntent, 0); PendingIntent declinePIntent = PendingIntent.getService(mContext, 0, declineIntent, 0); builder.addAction(android.R.drawable.ic_menu_close_clear_cancel, mContext.getString(R.string.decline), declinePIntent); builder.addAction(android.R.drawable.ic_menu_save, mContext.getString(R.string.accept), acceptPIntent); if (message instanceof FileMessage) { FileMessage fm = (FileMessage) message; StringBuilder sb = new StringBuilder(); sb.append(mContext.getString(R.string.buddy_sends_files, buddy.getSafeName())); for (FileInfo fi : fm.getFiles()) { sb.append("\n"); sb.append(mContext.getString(R.string.file_transfer_request_format, fi.getFilename(), ViewUtils.humanReadableByteCount(fi.getSize(), true))); } text = sb.toString(); builder.setSmallIcon(R.drawable.ic_file_message); } else { builder.setSmallIcon(R.drawable.ic_service_message); text = message.getText(); } } else { text = message.getText(); builder.setSmallIcon(R.drawable.ic_service_message); } PendingIntent contentIntent = PendingIntent.getActivity(mContext, 0, notificationIntent, 0); builder.setContentIntent(contentIntent); builder.setContentText(text); builder.setTicker(mContext.getString(R.string.default_key_value_format, buddy.getSafeName(), text)); builder.setLargeIcon(ViewUtils.getIcon(mContext, buddy.getFilename())); switch (mLEDNotificationMode) { case DEFAULT_BLINK: builder.setLights(0xff0000ff, 700, 300); break; case NATIVE_BLINK: if (!checkAndRunNativeLed()) { builder.setLights(0xff0000ff, 700, 300); } break; default: break; } if (!(message instanceof TextMessage)) { mNotificatorManager.notify((int) message.getMessageId(), builder.build()); } else { switch (mStatusBarMode) { case OFF: break; case ACCOUNTS: mNotificatorManager.notify(account.getAccountId().hashCode(), builder.build()); break; default: mNotificatorManager.notify(buddy.getFilename().hashCode(), builder.build()); } } // mNotificatorManager.cancel(buddy.getFilename().hashCode()); // mNotificatorManager.cancel(account.getAccountId().hashCode()); } private void fillMessageIntent(Intent notificationIntent, Account account, Buddy buddy, Message message) { notificationIntent.putExtra(Constants.INTENT_EXTRA_CLASS_NAME, message.getClass().getName()); notificationIntent.putExtra(Constants.INTENT_EXTRA_ACCOUNT, account); notificationIntent.putExtra(Constants.INTENT_EXTRA_BUDDY, buddy); notificationIntent.putExtra(Constants.INTENT_EXTRA_MESSAGE, message); notificationIntent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); } public void onAccountStateChanged(List<AccountService> accountServices) { switch (mStatusBarMode) { case ACCOUNTS: mNotificatorManager.cancel(APP_ICON_ID); // order of application bar icons is changed in Android 2.3 if (Build.VERSION.SDK_INT < 9) { for (AccountService as : accountServices) { accountNotification(as); } } else { for (int i=accountServices.size()-1; i>=0; i--) { accountNotification(accountServices.get(i)); } } break; case APP_ICON: NotificationCompat.Builder builder = new NotificationCompat.Builder(mContext); int onlines = 0; int offlines = 0; for (AccountService as : accountServices) { if (as != null && as.getAccount().isEnabled()) { if (as.getAccount().getConnectionState() == ConnectionState.CONNECTED) { onlines++; } else { offlines++; } } mNotificatorManager.cancel(as.getAccount().getAccountId().hashCode()); } StringBuilder contentText = new StringBuilder(); if (onlines > 0) { contentText.append(onlines); contentText.append(" "); contentText.append(mContext.getString(R.string.online)); } if (onlines > 0 && offlines > 0) { contentText.append(", "); } if (offlines > 0) { contentText.append(offlines); contentText.append(" "); contentText.append(mContext.getString(R.string.offline)); } Intent notificationIntent = new Intent(mContext, MainActivity.class); PendingIntent contentIntent = PendingIntent.getActivity(mContext, 0, notificationIntent, 0); builder.setOngoing(true); builder.setSmallIcon(R.drawable.ic_logo_notification); builder.setLargeIcon(BitmapFactory.decodeResource(mContext.getResources(), R.drawable.ic_launcher)); builder.setContentTitle(mContext.getString(R.string.app_name)); builder.setContentText(contentText.toString()); builder.setContentIntent(contentIntent); mNotificatorManager.notify(APP_ICON_ID, builder.build()); break; default: mNotificatorManager.cancel(APP_ICON_ID); for (AccountService as : accountServices) { if (as != null) mNotificatorManager.cancel(as.getAccount().getAccountId().hashCode()); } break; } } @SuppressLint("InlinedApi") private void accountNotification(AccountService accountService) { if (accountService == null) return; Account account = accountService.getAccount(); Logger.log("Notification for account " + account, LoggerLevel.VERBOSE); if (!account.isEnabled()) { removeAccountIcon(accountService.getAccount()); } else { NotificationCompat.Builder builder = new NotificationCompat.Builder(mContext); int unreads = account.getUnreadMessages(); int targetWidth = Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB ? mContext.getResources().getDimensionPixelSize(android.R.dimen.notification_large_icon_width) : 0; builder.setAutoCancel(false); builder.setOngoing(true); builder.setContentTitle(account.getSafeName()); builder.setContentText(unreads > 0 ? mContext.getString(R.string.unread_messages) : ViewUtils.getAccountStatusName(mContext, account, accountService.getProtocolService().getResources(false))); builder.setLargeIcon(ViewUtils.getIcon(mContext, account.getFilename(), targetWidth, 0)); builder.setSmallIcon(unreads > 0 ? R.drawable.ic_message : ViewUtils.getAccountStatusIcon(mContext, account)); builder.setNumber(unreads); Intent notificationIntent = new Intent(mContext, MainActivity.class); String notificatorId = Page.getPageIdForEntityWithId(ContactList.class, account); notificationIntent.setData((Uri.parse(URI_PROTOCOL + notificatorId))); PendingIntent contentIntent = PendingIntent.getActivity(mContext, account.getServiceId(), notificationIntent, 0); builder.setContentIntent(contentIntent); mNotificatorManager.notify(account.getAccountId().hashCode(), builder.build()); } } public void onFileTransferProgress(FileProgress progress) { Logger.log("Notification for file transfer " + progress.getFilePath() + ", id #" + progress.getMessageId() + " size " + ViewUtils.humanReadableByteCount(progress.getSentBytes(), true) + "/" + ViewUtils.humanReadableByteCount(progress.getTotalSizeBytes(), true), LoggerLevel.VERBOSE); NotificationCompat.Builder builder = mFileTransferViews.get(progress.getMessageId()); if (builder == null) { //mNotificatorManager.cancel((int) progress.getMessageId()); builder = new NotificationCompat.Builder(mContext); String path = progress.getFilePath(); builder.setContentTitle(path.contains(File.separator) ? path.substring(path.lastIndexOf(File.separator) + 1) : path); builder.setSmallIcon(R.drawable.ic_file_message); mFileTransferViews.put(progress.getMessageId(), builder); } builder.setWhen(System.currentTimeMillis()); if (TextUtils.isEmpty(progress.getError())) { if (progress.getSentBytes() >= progress.getTotalSizeBytes()) { PendingIntent contentIntent = PendingIntent.getActivity(mContext, 0, ViewUtils.getOpenFileInCorrespondingApplicationIntent(progress.getFilePath()), 0); builder.setContentIntent(contentIntent); builder.setAutoCancel(true); builder.setProgress(0, 0, false); builder.setContentText(mContext.getString(R.string.file_transfer_completed)); mFileTransferViews.remove(progress.getMessageId()); } else { builder.setAutoCancel(false); Intent intent = new Intent(mContext, MainActivity.class); PendingIntent contentIntent = PendingIntent.getActivity(mContext, 0, intent, 0); builder.setContentIntent(contentIntent); if (progress.getTotalSizeBytes() > 0) { int percentDone = (int) ((progress.getSentBytes() * 100f) / progress.getTotalSizeBytes()); builder.setProgress(100, percentDone, false); builder.setContentText(mContext.getString(R.string.file_transfer_bytes_completed, Long.toString(progress.getSentBytes()), Long.toString(progress.getTotalSizeBytes()))); } else { builder.setProgress(100, 0, true); builder.setContentText(mContext.getString(R.string.file_transfer_init)); } } } else { Intent intent = new Intent(mContext, MainActivity.class); PendingIntent contentIntent = PendingIntent.getActivity(mContext, 0, intent, 0); builder.setContentIntent(contentIntent); builder.setContentText(progress.getError()); builder.setAutoCancel(true); builder.setProgress(100, 100, false); mFileTransferViews.remove(progress.getMessageId()); } mNotificatorManager.notify((int) progress.getMessageId(), builder.build()); } public void removeAccountIcon(Account account) { Logger.log("Remove notification for account " + account, LoggerLevel.VERBOSE); mNotificatorManager.cancel(account.getAccountId().hashCode()); } public void removeMessageNotification(Buddy buddy, List<AccountService> services) { Logger.log("Remove message notification for " + buddy, LoggerLevel.VERBOSE); switch (mStatusBarMode) { case ACCOUNTS: onAccountStateChanged(services); break; default: mNotificatorManager.cancel(buddy.getFilename().hashCode()); break; } if (mLedBlinker != null) { mLedBlinker.stopBlinking(); } } public void removeFileNotification(long messageId) { Logger.log("Remove file transfer notification for id #" + messageId, LoggerLevel.VERBOSE); Builder n = mFileTransferViews.get(messageId); if (n != null) { mNotificatorManager.cancel(n.hashCode()); mFileTransferViews.remove(n); } } public void removeAppIcon() { Logger.log("Remove app icon notification", LoggerLevel.VERBOSE); mNotificatorManager.cancel(APP_ICON_ID); } private void play(final int res) { Executors.defaultThreadFactory().newThread(new Runnable() { @Override public void run() { MediaPlayer mp = MediaPlayer.create(mContext, res); mp.setVolume(mVolumeLevel, mVolumeLevel); mp.start(); } }).start(); } public void messageSound() { switch (mSoundMode) { case OFF: break; case PROFILE_DEPENDENT: playMessageBasedOnProfile(); break; default: playMessage(mSoundMode == SoundNotificationMode.SOUND || mSoundMode == SoundNotificationMode.SOUND_VIBRA, mSoundMode == SoundNotificationMode.VIBRA || mSoundMode == SoundNotificationMode.SOUND_VIBRA); break; } } private void playMessage(boolean playSound, boolean vibrate) { if (mAudioManager != null && playSound) { play(R.raw.message); } if (mVibrator != null && vibrate) { mVibrator.vibrate(800); } } private void playMessageBasedOnProfile() { int ringerMode = mAudioManager.getRingerMode(); switch (ringerMode) { case AudioManager.RINGER_MODE_NORMAL: playMessage(true, true); break; case AudioManager.RINGER_MODE_VIBRATE: playMessage(false, true); break; case AudioManager.RINGER_MODE_SILENT: playMessage(false, false); break; } } public void processException(Exception ex, Account account) { Logger.log("Notification for exception " + ex, LoggerLevel.VERBOSE); Builder builder = new Builder(mContext); Notification n = null; if (ex instanceof AceImException) { String errorText = null; switch (((AceImException) ex).reason) { case NO_PROTOCOL_FOUND: errorText = mContext.getString(R.string.error_no_x_protocol_found, account.getProtocolName()); Intent i = ViewUtils.getSearchPluginsInPlayStoreIntent(account); PendingIntent pi = PendingIntent.getActivity(mContext, 0, i, PendingIntent.FLAG_CANCEL_CURRENT); builder.setAutoCancel(true) .setSmallIcon(android.R.drawable.ic_dialog_alert) .setWhen(System.currentTimeMillis()) .setDefaults(Notification.DEFAULT_SOUND) .setContentIntent(pi) .setTicker(account.getSafeName()) .setContentText(errorText) .setContentTitle(account.getSafeName()); n = builder.build(); break; default: break; } } else if (ex instanceof ProtocolException) { String errorText = null; switch (((ProtocolException) ex).cause) { case BROKEN_AUTH_DATA: errorText = mContext.getString(R.string.error_broken_auth_data); break; case CONNECTION_ERROR: errorText = mContext.getString(R.string.error_connection_failed); break; case GROUPCHAT_ALREADY_EXISTS: errorText = mContext.getString(R.string.error_groupchat_already_exists); break; case NONE: return; case NO_GROUPCHAT_AVAILABLE: errorText = mContext.getString(R.string.error_no_groupchat_available); break; case CANNOT_AUTHORIZE: errorText = mContext.getString(R.string.error_cannot_authorize); break; case CONNECTION_LIMIT_EXCEEDED: errorText = mContext.getString(R.string.error_connection_limit_exceeded); break; default: errorText = ex.getLocalizedMessage(); break; } builder.setAutoCancel(true) .setSmallIcon(android.R.drawable.ic_dialog_alert) .setWhen(System.currentTimeMillis()) .setDefaults(Notification.DEFAULT_SOUND) .setTicker(account.getSafeName()) .setContentText(errorText) .setContentTitle(account.getSafeName()); n = builder.build(); } if (n != null) { mNotificatorManager.notify(n.hashCode(), n); } } public void setMessageSoundOnly(boolean messageSoundOnly) { this.messageSoundOnly = messageSoundOnly; } /** * @return the messageSoundOnly */ public boolean isMessageSoundOnly() { return messageSoundOnly; } /** * @return the mSoundMode */ public SoundNotificationMode getSoundMode() { return mSoundMode; } /** * @param soundMode * the mSoundMode to set */ public void setSoundMode(SoundNotificationMode soundMode) { if (soundMode == null) { return; } this.mSoundMode = soundMode; } /** * @return the mVolumeLevel */ public float getVolumeLevel() { return mVolumeLevel; } /** * @param mVolumeLevel the mVolumeLevel to set */ public void setVolumeLevel(float mVolumeLevel) { this.mVolumeLevel = mVolumeLevel; } /** * @return the mStatusBarMode */ public StatusBarNotificationMode getStatusBarMode() { return mStatusBarMode; } /** * @param statusBarMode * the mStatusBarMode to set */ public void setStatusBarMode(StatusBarNotificationMode statusBarMode) { if (statusBarMode == null) { return; } this.mStatusBarMode = statusBarMode; } public LEDNotificationMode getLEDNotificationMode() { return mLEDNotificationMode; } public void setLEDNotificationMode(LEDNotificationMode ledNotificationMode) { if (ledNotificationMode == null) { return; } this.mLEDNotificationMode = ledNotificationMode; } private boolean checkAndRunNativeLed() { mLedBlinker = LedBlinker.getLedBlinker(); return mLedBlinker != null; } public static enum SoundNotificationMode { OFF, SOUND, VIBRA, SOUND_VIBRA, PROFILE_DEPENDENT; } public static enum StatusBarNotificationMode { OFF, APP_ICON, MESSAGES, ACCOUNTS; } public static enum LEDNotificationMode { OFF, DEFAULT_BLINK, NATIVE_BLINK } private static class LedBlinker extends Thread { private final String red; private final String green; private final String blue; // volatile boolean stopped = false; LedBlinker(boolean isSE) { if (isSE) { red = "ledc:rgb1:red"; green = "ledc:rgb1:green"; blue = "ledc:rgb1:blue"; } else { red = "amber"; green = "green"; blue = "blue"; } } public static LedBlinker getLedBlinker() { Logger.log("Init LED blinker... ", LoggerLevel.VERBOSE); boolean isSE = true; // sony ericsson case try { nativeLedControl("ledc:rgb1:green", 0); Logger.log("...LED blinker for SE inited", LoggerLevel.VERBOSE); } catch (Exception e) { isSE = false; try { nativeLedControl("green", 0); Logger.log("...LED blinker inited", LoggerLevel.VERBOSE); } catch (Exception e1) { Logger.log("... no support available", LoggerLevel.VERBOSE); return null; } } LedBlinker blinker = new LedBlinker(isSE); return blinker; } public void stopBlinking() { Logger.log("Stop LED blinker", LoggerLevel.VERBOSE); // stopped = true; interrupt(); } /** * /sys/class/leds/ledc:rgb1:red/brightness - red * /sys/class/leds/ledc:rgb1:green/brightness - green * /sys/class/leds/ledc:rgb1:blue/brightness - blue */ private static void nativeLedControl(String name, int brightness) throws Exception { FileWriter fw = new FileWriter("/sys/class/leds/" + name + "/brightness"); fw.write(Integer.toString(brightness)); fw.close(); } public void run() { for (int ledState = 0;; ledState = (ledState + 1) % 6) { switch (ledState) { case 0: try { nativeLedControl(red, 255); } catch (Exception e1) { } break; case 1: try { nativeLedControl(blue, 255); } catch (Exception e1) { } break; case 2: try { nativeLedControl(red, 0); } catch (Exception e1) { } break; case 3: try { nativeLedControl(green, 255); } catch (Exception e1) { } break; case 4: try { nativeLedControl(blue, 0); } catch (Exception e1) { } break; case 5: try { nativeLedControl(green, 0); } catch (Exception e1) { } break; } try { Thread.sleep(1000); } catch (InterruptedException e) { break; } } try { nativeLedControl(red, 0); } catch (Exception e) { } try { nativeLedControl(green, 0); } catch (Exception e) { } try { nativeLedControl(blue, 0); } catch (Exception e) { } } } }